import fs from "node:fs/promises";
import path from "node:path";

const WORKFLOW_ROOT = path.resolve(__dirname, "..", "..");
const DOCS_INDEX_PATH = path.join(WORKFLOW_ROOT, "docs-index.json");
const REPO_ROOT = path.resolve(WORKFLOW_ROOT, "..");

export interface SearchDocsOptions {
  phase?: number | string;
  keyword?: string;
  limit?: number;
}

export interface DocHit {
  path: string;
  anchor?: string;
  content: string;
  relevance: number;
}

interface DocsIndexEntry {
  path: string;
  anchors?: string[];
  relevance?: Record<string, number>;
}

function normalisePhase(value?: number | string): string | undefined {
  if (value === undefined || value === null) {
    return undefined;
  }
  if (typeof value === "number") {
    return `phase${value}`;
  }
  const lower = value.toString().toLowerCase();
  return lower.startsWith("phase") ? lower : `phase${lower}`;
}

function splitSections(content: string): Array<{ heading: string; body: string }> {
  const lines = content.split(/\r?\n/);
  const sections: Array<{ heading: string; body: string }> = [];
  let currentHeading = "__root__";
  let buffer: string[] = [];

  const pushSection = () => {
    sections.push({ heading: currentHeading, body: buffer.join("\n").trim() });
    buffer = [];
  };

  for (const line of lines) {
    const headingMatch = /^(#+)\s*(.+)$/.exec(line.trim());
    if (headingMatch) {
      if (buffer.length > 0 || sections.length === 0) {
        pushSection();
      }
      currentHeading = headingMatch[2].trim();
    } else {
      buffer.push(line);
    }
  }

  pushSection();
  return sections.filter((section) => section.body.length > 0);
}

function resolveDocPath(docPath: string): string {
  if (path.isAbsolute(docPath)) {
    return docPath;
  }
  if (docPath.startsWith("Workflow/")) {
    return path.join(REPO_ROOT, docPath);
  }
  return path.join(WORKFLOW_ROOT, docPath);
}

export async function readDocSections(docPath: string, sections?: string[]): Promise<string> {
  const absolutePath = resolveDocPath(docPath);
  const fileContent = await fs.readFile(absolutePath, "utf-8");

  if (!sections || sections.length === 0) {
    return fileContent;
  }

  const wanted = new Set(sections.map((s) => s.trim().toLowerCase()));
  const parsedSections = splitSections(fileContent);
  const matches = parsedSections.filter((section) => wanted.has(section.heading.trim().toLowerCase()));

  if (matches.length === 0) {
    return "";
  }

  return matches.map((section) => `# ${section.heading}\n${section.body}`.trim()).join("\n\n");
}

async function loadDocsIndex(): Promise<DocsIndexEntry[]> {
  const raw = await fs.readFile(DOCS_INDEX_PATH, "utf-8");
  return JSON.parse(raw) as DocsIndexEntry[];
}

export async function searchDocs(options: SearchDocsOptions): Promise<DocHit[]> {
  const { phase, keyword, limit = 5 } = options;
  const normalisedPhase = normalisePhase(phase);
  const entries = await loadDocsIndex();
  const hits: DocHit[] = [];
  const needle = keyword ? keyword.toLowerCase() : undefined;

  for (const entry of entries) {
    if (normalisedPhase && entry.relevance) {
      const score = entry.relevance[normalisedPhase];
      if (score !== undefined && score <= 0) {
        continue;
      }
      if (score === undefined && normalisedPhase) {
        continue;
      }
    }

    const absolutePath = resolveDocPath(entry.path);
    let content: string;
    try {
      content = await fs.readFile(absolutePath, "utf-8");
    } catch {
      continue;
    }

    if (needle) {
      const lowerContent = content.toLowerCase();
      if (!lowerContent.includes(needle)) {
        continue;
      }
    }

    const trimmed = needle
      ? extractSnippet(content, needle)
      : content.slice(0, 500);
    hits.push({
      path: entry.path,
      content: trimmed,
      relevance: phaseScore(entry, normalisedPhase),
    });

    if (hits.length >= limit) {
      break;
    }
  }

  return hits;
}

function extractSnippet(content: string, needle: string): string {
  const lower = content.toLowerCase();
  const index = lower.indexOf(needle);
  if (index === -1) {
    return content.slice(0, 400);
  }
  const start = Math.max(index - 150, 0);
  const end = Math.min(index + needle.length + 150, content.length);
  return content.slice(start, end).trim();
}

function phaseScore(entry: DocsIndexEntry, phase?: string): number {
  if (!phase) {
    return 1;
  }
  if (!entry.relevance) {
    return 1;
  }
  return entry.relevance[phase] ?? 0.5;
}
